#include "HiliteConfig.h"
#include <assert.h>

/**************************************************** 
 * Implementation for CToken                        *
 ****************************************************/
CTokens::CTokens() : CStringArray(TOKEN_INIT_COUNT)
{
    memset (m_pArray, 0, sizeof(char*) * m_nCount);
    m_uTokenCount = 0;
}

CTokens::~CTokens()
{
    unsigned int i;
    for (i = 0; i < m_uTokenCount; i++) {
        assert (m_pArray[i]);
        free (m_pArray[i]);
    }
}

bool CTokens::FindToken (const char* str, unsigned int* index)
{
    assert (m_pArray && m_nCount > 0);
    assert (m_uTokenCount <= m_nCount);
    assert (index);

    /* Using binary search.*/
    unsigned int lower = 0;
    unsigned int upper = m_uTokenCount-1;
    int cmp_result;

    if (!m_uTokenCount) {
        *index = 0;
        return false;
    }

    cmp_result = strcmp (str, m_pArray[lower]);
    if (cmp_result <= 0) {
        /* str <= m_pArray[lower]*/
        *index = lower;
        return (cmp_result == 0);
    }
    cmp_result = strcmp (str, m_pArray[upper]);
    if (cmp_result >= 0) {
        /* str >= m_pArray[lower]*/
        *index = upper;
        return (cmp_result == 0);
    }

    while (lower < upper) {
        unsigned int mid = (lower + upper) / 2 ;
        if (mid == lower) {
            *index = lower;
            return false;
        }

        cmp_result = strcmp(str, m_pArray[mid]);

        if (cmp_result == 0) {
            *index = mid;
            return true;
        } else if (cmp_result < 0) {
            upper = mid;
        } else if (cmp_result > 0) {
            lower = mid;
        }
    }

    assert (lower < m_uTokenCount);
    *index = lower;
    return (strcmp (str, m_pArray[lower]) == 0);
}

bool CTokens::FindToken (const char* str)
{
    unsigned int foo;
    return FindToken(str, &foo);
}

const char* CTokens::GetToken(unsigned int idx)
{
    assert (idx < m_uTokenCount);
    assert (m_uTokenCount < m_nCount);
    assert (m_pArray);

    return (m_pArray[idx]);
}

int CTokens::AddToken (const char* str)
{
    unsigned int index;
    bool found = FindToken (str, &index);

    if (found) {
        return 0;
    }

    if (m_uTokenCount == m_nCount) {
        int ret = Resize(m_nCount + TOKEN_GROW_STEP);
        if (ret != 0) {
            return ret;
        }
    }
    
    assert (m_uTokenCount < m_nCount);
    char* string = (char*) malloc (strlen(str) + 1);
    if (!string) {
        return -1;
    }

    strcpy(string, str);
    if (index == 0 && !m_pArray[index]) {
        InsertAt(index, 1, &string);
        m_uTokenCount++;
        return 0;
    }
    assert (m_pArray[index] != NULL);
    int cmp_ret = strcmp(str, m_pArray[index]);
    assert (cmp_ret != 0);
    if (cmp_ret < 0) {
        InsertAt(index, 1, &string);
    } else {
        InsertAt(index+1, 1, &string);
    }
    m_uTokenCount++;
    return 0;
}

/**************************************************** 
 * Implementation for CSyntaxConfig                 *
 ****************************************************/
CSyntaxConfig::CSyntaxConfig()
{
    m_Delimiter = NULL;
    m_Quote = NULL;
    m_Escape = '\0';
    m_BlockCommentOn = NULL;
    m_BlockCommentOff = NULL;
    m_LineComment = NULL;
}

CSyntaxConfig::~CSyntaxConfig()
{
    if (m_Delimiter) {
        free (m_Delimiter);
    }
    if (m_Quote) {
        free (m_Quote);
    }
    if (m_BlockCommentOn) {
        free (m_BlockCommentOn);
    }
    if (m_BlockCommentOff) {
        free (m_BlockCommentOff);
    }
    if (m_LineComment) {
        free (m_LineComment);
    }
}

int CSyntaxConfig::SetDelimiters (const char* str)
{
    assert (str);

    if (m_Delimiter) {
        free (m_Delimiter);
    }
    m_Delimiter = (char*) malloc (strlen(str)+1);
    if (!m_Delimiter) {
        return -1;
    }
    strcpy (m_Delimiter, str);
    return 0;
}

int CSyntaxConfig::AddFileExtension (const char* s)
{
    assert (s);
    return m_FileExtension.AddToken(s);
}

int CSyntaxConfig::AddIndent (const char* s)
{
    assert (s);
    return m_Indent.AddToken(s);
}

int CSyntaxConfig::AddUnindent (const char* s)
{
    assert (s);
    return m_Unindent.AddToken(s);
}

int CSyntaxConfig::SetBlockCommentOn (const char* s)
{
    assert (s);
    if (m_BlockCommentOn) {
        free (m_BlockCommentOn);
    }

    m_BlockCommentOn = (char*)malloc(strlen(s) + 1);
    if (!m_BlockCommentOn) {
        return -1;
    }
    strcpy (m_BlockCommentOn, s);
    return 0;
}

int CSyntaxConfig::SetBlockCommentOff (const char* s)
{
    assert (s);
    if (m_BlockCommentOff) {
        free (m_BlockCommentOff);
    }

    m_BlockCommentOff = (char*)malloc(strlen(s) + 1);
    if (!m_BlockCommentOff) {
        return -1;
    }
    strcpy (m_BlockCommentOff, s);
    return 0;
}

int CSyntaxConfig::SetLineComment (const char* s)
{
    assert (s);
    if (m_LineComment) {
        free (m_LineComment);
    }

    m_LineComment = (char*)malloc(strlen(s) + 1);
    if (!m_LineComment) {
        return -1;
    }
    strcpy (m_LineComment, s);
    return 0;
}

int CSyntaxConfig::AddKeyword (const char* s)
{
    assert(s);
    return m_Keywords.AddToken(s);
}

int CSyntaxConfig::SetEscape (const char* s)
{
    assert(s);
    m_Escape = s[0];
    return 0;
}

int CSyntaxConfig::SetQuote (const char* s)
{
    assert(s);
    if (m_Quote) {
        free (m_Quote);
    }

    m_Quote = (char*)malloc(strlen(s) + 1);
    if (!m_Quote) {
        return -1;
    }
    strcpy (m_Quote, s);
    return 0;
}

/**************************************************** 
 * Implementation for CSyntaxConfigArray            *
 ****************************************************/
SyntaxField g_SyntaxFields[] = 
            {   {LANG_STR, LANG_ID},
                {FILEEXT_STR, FILEEXT_ID},
                {DELIMITER_STR, DELIMITER_ID},
                {INDENT_STR, INDENT_ID},
                {UNINDENT_STR, UNINDENT_ID},
                {BCOMMENTON_STR, BCOMMENTON_ID},
                {BCOMMENTOFF_STR, BCOMMENTOFF_ID},
                {LCOMMENT_STR, LCOMMENT_ID},
                {KEYWORD_STR, KEYWORD_ID},
                {ESCCHAR_STR, ESCCHAR_ID},
                {QUOTE_STR, QUOTE_ID},
                {NULL, -1}
            };

CSyntaxConfigArray::CSyntaxConfigArray() : CSyntaxArray (SYNTAX_INIT)
{
    m_uSyntaxCount = 0;
}

CSyntaxConfigArray::~CSyntaxConfigArray()
{
    RemoveAllSyntax();
}

int CSyntaxConfigArray::AddNewSyntax()
{
    assert (m_pArray);
    assert (m_uSyntaxCount <= m_nCount);

    if (m_uSyntaxCount == m_nCount) {
        int ret = Resize(m_nCount + SYNTAX_GROW);
        if (ret != 0) {
            return ret;
        }
    }
    CSyntaxConfig* syn = new CSyntaxConfig();
    if (!syn) {
        return 0;
    }
    m_pArray[m_uSyntaxCount] = syn;
    return (m_uSyntaxCount++);
}

int CSyntaxConfigArray::FindSyntaxByFileExtension(const char* file_ext, CSyntaxConfig** config)
{
    assert (file_ext);
    assert (config);

    int idx;
    for (idx = 0; (unsigned int)idx < m_uSyntaxCount; idx++) {
        assert (m_pArray[idx]);
        unsigned int dummy;
        if (m_pArray[idx]->m_FileExtension.FindToken(file_ext, &dummy)) {
            *config = m_pArray[idx];
            return idx;
        }
    }
    *config = NULL;
    return -1;
}

#define MIN(a,b) (a<b? a: b)

int CSyntaxConfigArray::FindSyntaxByFilename(const char* filename, CSyntaxConfig** config)
{
    assert (filename);
    assert (config);

    char ext[_MAX_PATH+1];
    size_t len = strlen(filename);
    size_t idx = len;
    *config = NULL;
    if (idx == 0) {
        return -1;
    }
    memset (ext, 0, _MAX_PATH+1);
    while (idx > 0 && filename[--idx] != '.');
    strncpy (ext, filename+idx+1, MIN(_MAX_PATH, len-idx));
    return FindSyntaxByFileExtension(ext, config);
}

#define BUF_SIZE 512

int CSyntaxConfigArray::ReadFromFile(const char* file)
{
    assert (file);
    char buf[BUF_SIZE];

    RemoveAllSyntax ();
    if (Resize(SYNTAX_INIT) < 0) {
        return -1;
    }
    
    FILE* f = fopen(file, "rb");
    if (!f) {
        return -2;
    }
    
    int ret = GetWordFromFile(f, buf, sizeof(buf));
    int state = GetSyntaxFieldsID(g_SyntaxFields, buf);

    while (ret >= 0) {
        switch (state) {
        case LANG_ID:
            ret = ProcessLanguage(f, &state);
            break;

        case FILEEXT_ID:
            ret = ProcessFileExtension(f, &state);
            break;

        case DELIMITER_ID:
            ret = ProcessDelimiter(f, &state);
            break;

        case INDENT_ID:
            ret = ProcessIndent(f, &state);
            break;

        case UNINDENT_ID:
            ret = ProcessUnindent(f, &state);
            break;

        case BCOMMENTON_ID:
            ret = ProcessBlockCommentOn(f, &state);
            break;

        case BCOMMENTOFF_ID:
            ret = ProcessBlockCommentOff(f, &state);
            break;

        case LCOMMENT_ID:
            ret = ProcessLineComment(f, &state);
            break;

        case KEYWORD_ID:
            ret = ProcessKeyword (f, &state);
            break;
        
        case ESCCHAR_ID:
            ret = ProcessEscape (f, &state);
            break;

        case QUOTE_ID:
            ret = ProcessQuote (f, &state);
            break;

        case OTHER_ID:
            ret = GetWordFromFile(f, buf, sizeof(buf));
            if (ret >= 0) {
                state = GetSyntaxFieldsID(g_SyntaxFields, buf);
            }
            break;
            
        default:
            assert(0);
        }
    }
    fclose(f);
    return 0;
}

void CSyntaxConfigArray::RemoveAllSyntax()
{
    assert (m_pArray);
    assert (m_uSyntaxCount <= m_nCount);

    unsigned int i;
    for (i = 0; i < m_uSyntaxCount; i++) {
        if ( m_pArray[i] ) {
            delete (m_pArray[i]);
            m_pArray[i] = NULL;
        }
    }
    m_uSyntaxCount = 0;
}

int CSyntaxConfigArray::ProcessLanguage(FILE* f, int* state)
{
    assert (f);
    assert (state);

    char buf[BUF_SIZE];
    int ret;

    *state = OTHER_ID;
    do {    
        ret = GetWordFromFile(f, buf, sizeof(buf)); 
        if (ret < 0) {  
            return -1;  
        }   
        *state = GetSyntaxFieldsID(g_SyntaxFields, buf);    
    } while (*state == OTHER_ID);   
    return AddNewSyntax();       
}

#define PROCESS_TOKEN_FIELD(func) do {\
                                        assert (f); \
                                        assert (state); \
                                        \
                                        int ret;    \
                                        char buf[BUF_SIZE]; \
                                        *state = OTHER_ID;  \
                                        \
                                        do {    \
                                            ret = GetWordFromFile(f, buf, sizeof(buf)); \
                                            if (ret < 0) {  \
                                                return -1;  \
                                            }   \
                                            *state = GetSyntaxFieldsID(g_SyntaxFields, buf);    \
                                            if (*state == OTHER_ID) {   \
                                                ret = m_pArray[m_uSyntaxCount-1]->func(buf);    \
                                            }    \
                                        } while (*state == OTHER_ID && ret >= 0);   \
                                        return 0;   \
                                    } while (0);    

int CSyntaxConfigArray::ProcessFileExtension(FILE* f, int* state)
{
    PROCESS_TOKEN_FIELD(AddFileExtension);
    assert (0);
    return 0;
}

int CSyntaxConfigArray::ProcessDelimiter(FILE* f, int* state)
{
    assert (f);
    assert (state);

    int ret;
    char buf[BUF_SIZE];
    *state = OTHER_ID;

    ret = GetStringFromFile(f, buf, sizeof(buf));
    if (ret < 0) {
        return -1;
    }
    return m_pArray[m_uSyntaxCount-1]->SetDelimiters(buf);
}

int CSyntaxConfigArray::ProcessIndent(FILE* f, int* state)
{
    PROCESS_TOKEN_FIELD(AddIndent);
    assert (0);
    return 0;
}

int CSyntaxConfigArray::ProcessUnindent(FILE* f, int* state)
{
    PROCESS_TOKEN_FIELD(AddUnindent);
    assert (0);
    return 0;
}

int CSyntaxConfigArray::ProcessBlockCommentOn(FILE* f, int* state)
{
    PROCESS_TOKEN_FIELD(SetBlockCommentOn);
    assert (0);
    return 0;
}

int CSyntaxConfigArray::ProcessBlockCommentOff(FILE* f, int* state)
{
    PROCESS_TOKEN_FIELD(SetBlockCommentOff);
    assert (0);
    return 0;
}

int CSyntaxConfigArray::ProcessLineComment(FILE* f, int* state)
{
    PROCESS_TOKEN_FIELD(SetLineComment);
    assert (0);
    return 0;
}

int CSyntaxConfigArray::ProcessKeyword(FILE* f, int* state)
{
    PROCESS_TOKEN_FIELD(AddKeyword);
    assert (0);
    return 0;
}

int CSyntaxConfigArray::ProcessEscape(FILE* f, int* state)
{
    PROCESS_TOKEN_FIELD(SetEscape);
    assert (0);
    return 0;
}

int CSyntaxConfigArray::ProcessQuote(FILE* f, int* state)
{
    PROCESS_TOKEN_FIELD(SetQuote);
    assert (0);
    return 0;
}

#define IS_WHITE_SPACE(c) (c == ' ' || c == '\t' || c == 0x0A || c == 0x0D)

int GetWordFromFile(FILE* f, char* buf, size_t size)
{
    assert (buf);
    assert (f);
    assert (size > 1);

    unsigned int i = 0;
    char c;
    int ret;
    memset (buf, 0, size);

    do {
        ret = fread(&c, 1, 1, f);
    } while (IS_WHITE_SPACE(c) && ret == 1);
    if (ret < 1) {
        return -1;
    };

    do {
        if (IS_WHITE_SPACE(c)) {
            break;
        }
        buf[i++] = c;
    } while (i < size-1 && (fread(&c, 1, 1, f) == 1));
    assert (i!= 0);
    return 0;
}

int GetStringFromFile(FILE* f, char* buf, size_t size)
{
    assert (buf);
    assert (f);
    assert (size > 1);

    unsigned int i = 0;
    char c;
    int ret;
    memset (buf, 0, size);

    do {
        ret = fread(&c, 1, 1, f);
    } while ((c ==' ' || c == '\t') && ret == 1);
    if (ret < 1) {
        return -1;
    };

    do {
        if (c == 0xa || c == 0x0d) {
            break;
        }
        buf[i++] = c;
        ret = fread(&c, 1, 1, f);
        if (ret < 1) {
            return -1;
        }
    } while (i < size-1);

    return 0;
}

int GetSyntaxFieldsID(const SyntaxField* sf, const char* str)
{
    int i = 0;
    while (sf[i].txt) {
        if (strcmp(sf[i].txt, str) == 0) {
            return sf[i].field_id;
        }
        i++;
    }
    return OTHER_ID;
}
